﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

using Poi.NET.poifs.common;

namespace Poi.NET.poifs.storage
{
/**
 * Storage for documents that are too small to use regular
 * DocumentBlocks for their data
 *
 * @author  Marc Johnson (mjohnson at apache dot org)
 */
public readonly class SmallDocumentBlock : BlockWritable, ListManagedBlock {
    private static readonly int BLOCK_SHIFT = 6;

    private byte[]            _data;
    private static readonly byte _default_fill         = ( byte ) 0xff;
    private static readonly int  _block_size           = 1 << BLOCK_SHIFT;
    private static readonly int BLOCK_MASK = _block_size - 1;

    private static readonly int  _blocks_per_big_block =
        POIFSConstants.BIG_BLOCK_SIZE / _block_size;

    private SmallDocumentBlock(byte [] data, int index) : this()
    {
        Array.Copy(data, index * _block_size, _data, 0, _block_size);
    }

    private SmallDocumentBlock()
    {
        _data = new byte[ _block_size ];
    }

    /**
     * convert a single long array into an array of SmallDocumentBlock
     * instances
     *
     * @param array the byte array to be converted
     * @param size the intended size of the array (which may be smaller)
     *
     * @return an array of SmallDocumentBlock instances, filled from
     *         the array
     */
    public static SmallDocumentBlock [] convert(byte [] array,
                                                int size)
    {
        SmallDocumentBlock[] rval   =
            new SmallDocumentBlock[ (size + _block_size - 1) / _block_size ];
        int                  offset = 0;

        for (int k = 0; k < rval.Length; k++)
        {
            rval[ k ] = new SmallDocumentBlock();
            if (offset < array.Length)
            {
                int length = Math.Min(_block_size, array.Length - offset);

                Array.Copy(array, offset, rval[ k ]._data, 0, length);
                if (length != _block_size)
                {
                    Arrays.fill(rval[ k ]._data, length, _block_size,
                                _default_fill);
                }
            }
            else
            {
                for (int i = 0; i < rval[k]._data.Length; i++)
                    rval[k]._data[i] = _default_fill;
                //Arrays.fill(rval[ k ]._data, _default_fill);
            }
            offset += _block_size;
        }
        return rval;
    }

    /**
     * fill out a List of SmallDocumentBlocks so that it fully occupies
     * a set of big blocks
     *
     * @param blocks the List to be filled out
     *
     * @return number of big blocks the list encompasses
     */
    public static int fill(List blocks)
    {
        int count           = blocks.size();
        int big_block_count = (count + _blocks_per_big_block - 1)
                              / _blocks_per_big_block;
        int full_count      = big_block_count * _blocks_per_big_block;

        for (; count < full_count; count++)
        {
            blocks.add(makeEmptySmallDocumentBlock());
        }
        return big_block_count;
    }

    /**
     * Factory for creating SmallDocumentBlocks from DocumentBlocks
     *
     * @param store the original DocumentBlocks
     * @param size the total document size
     *
     * @return an array of new SmallDocumentBlocks instances
     *
     * @exception IOException on errors reading from the DocumentBlocks
     * @exception ArrayIndexOutOfBoundsException if, somehow, the store
     *            contains less data than size indicates
     */
    public static SmallDocumentBlock [] convert(BlockWritable[] store,
                                                int size)
    {
        MemoryStream stream = new MemoryStream();
        StreamWriter sw = new StreamWriter(stream);

        for (int j = 0; j < store.Length; j++)
        {
            store[j].writeBlocks(sw.BaseStream);
        }

        byte[] data = stream.ToArray();
        SmallDocumentBlock[] rval =
            new SmallDocumentBlock[ convertToBlockCount(size) ];

        for (int index = 0; index < rval.Length; index++)
        {
            rval[ index ] = new SmallDocumentBlock(data, index);
        }
        return rval;
    }

    /**
     * create a list of SmallDocumentBlock's from raw data
     *
     * @param blocks the raw data containing the SmallDocumentBlock
     *               data
     *
     * @return a List of SmallDocumentBlock's extracted from the input
     */
    public static List extract(ListManagedBlock [] blocks)
    {
        List<SmallDocumentBlock> sdbs = new List<SmallDocumentBlock>();

        for (int j = 0; j < blocks.Length; j++)
        {
            byte[] data = blocks[ j ].getData();

            for (int k = 0; k < _blocks_per_big_block; k++)
            {
                sdbs.Add(new SmallDocumentBlock(data, k));
            }
        }
        return sdbs;
    }

    public static DataInputBlock getDataInputBlock(SmallDocumentBlock[] blocks, int offset) {
        int firstBlockIndex = offset >> BLOCK_SHIFT;
        int firstBlockOffset= offset & BLOCK_MASK;
        return new DataInputBlock(blocks[firstBlockIndex]._data, firstBlockOffset);
    }

    /**
     * Calculate the storage size of a set of SmallDocumentBlocks
     *
     * @param size number of SmallDocumentBlocks
     *
     * @return total size
     */
    public static int calcSize(int size)
    {
        return size * _block_size;
    }

    private static SmallDocumentBlock makeEmptySmallDocumentBlock()
    {
        SmallDocumentBlock block = new SmallDocumentBlock();

        for (int i = 0; i < block._data.Length; i++)
            block._data[i] = _default_fill;
        //Arrays.fill(block._data, _default_fill);
        return block;
    }

    private static int convertToBlockCount(int size)
    {
        return (size + _block_size - 1) / _block_size;
    }

    /**
     * Write the storage to an OutputStream
     *
     * @param stream the OutputStream to which the stored data should
     *               be written
     *
     * @exception IOException on problems writing to the specified
     *            stream
     */
    public void writeBlocks(StreamWriter stream)
    {
        stream.Write(_data);
    }

    /**
     * Get the data from the block
     *
     * @return the block's data as a byte array
     *
     * @exception IOException if there is no data
     */
    public byte [] getData() {
        return _data;
    }
}
}
